<?php
/**
 * Created by PhpStorm.
 * User: zkc
 * Date: 2018-10-12
 * Time: 09:45
 */

namespace common\utils\soap;

use common\extensions\eagleeye\EagleEye;
use common\utils\TimeUtils;
use Exception;
use yii\base\Component;
use yii\base\ErrorException;

class SoapClient extends Component
{
    public $name;

    /**
     * @var string $url the URL of the WSDL file.
     */
    public $url;
    /**
     * @var array the array of SOAP client options.
     */
    public $options = [
    ];

    /**
     * @var \SoapClient the SOAP client instance.
     */
    private $_client;

    private $context;

    /**
     * @inheritdoc
     * @throws SoapException
     */
    public function init()
    {
        parent::init();
        if (empty($this->url) && empty($this->options['location'])) {
            throw new SoapException('One of The "url" property or options[\'location\'] must be set.');
        }

        $this->context = stream_context_create();
        $this->options = array_merge($this->options, array('stream_context' => $this->context));

        // 必须开启此选项，才能获取发送接收的xml数据
        $this->options['trace']      = 1;
        $this->options['exception']  = 1;
        $this->options['user_agent'] = 'SoapClient';
        // 非生产环境不缓存wsdl
        if (!isset($this->options['cache_wsdl']) && (YII_DEBUG || !YII_ENV_PROD)) {
            $this->options['cache_wsdl'] = WSDL_CACHE_NONE;
        }

        try {
            $this->_client = new \SoapClient($this->url, $this->options);
        } catch (\SoapFault $e) {
            throw new SoapException($e->getMessage(), (int)$e->getCode(), $e);
        }
    }

    /**
     * @param string $name
     * @param array  $arguments
     *
     * @return mixed
     * @throws Exception
     * @throws \yii\base\Exception
     */
    public function __call($name, $arguments)
    {
        return $this->__soapCall($name, $arguments);
    }

    /**
     * @param            $function_name
     * @param array      $arguments
     * @param array|null $options
     * @param null       $input_headers
     * @param array|null $output_headers
     *
     * @return mixed|null
     * @throws ErrorException
     * @throws SoapException
     * @throws \yii\base\Exception
     */
    public function __soapCall(
        $function_name,
        array $arguments,
        array $options = null,
        $input_headers = null,
        array &$output_headers = null
    ) {
        $ret = null;
        $exp = null;


        $start_ts = microtime(true);

        // 增加请求头
        $eagleCall = EagleEye::nextCallWithHeaders();

        // 记录日志
        $log_data = [];

        $log_data['@timestamp'] = TimeUtils::ESDateTime($start_ts, 3);;

        $log_data['reqid'] = $_SERVER['REQ_ID'];
        $log_data['env']   = YII_ENV;

        // non-WSDL mode
        if (empty($this->url)) {
            $log_data['url'] = $this->options['location'];
        } else {
            // WSDL mode
            $log_data['url'] = $this->url;
        }


        $log_data['method'] = $function_name;
        $log_data['args']   = json_encode($arguments, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);

        try {
            // 追加trace请求头
            stream_context_set_option($this->context,
                array('http' => array('header' => $this->__encodeRequestHeaders($eagleCall['headers']))));

            $ret = $this->_client->__soapCall($function_name, $arguments, $options, $input_headers, $output_headers);
        } catch (Exception $e) {
            // TODO 处理SoapFault
            $exp = new SoapException($e->getMessage(), (int)$e->getCode(), $e);
        }

        $stop_ts = microtime(true);

        $log_data['usetime'] = sprintf("%.3f", $stop_ts - $start_ts);

        $log_data['request']          = $this->_client->__getLastRequest();
        $log_data['request.headers']  = $this->_client->__getLastRequestHeaders();
        $log_data['response']         = $this->_client->__getLastResponse();
        $log_data['response.headers'] = $this->_client->__getLastResponseHeaders();

        $log_data['code']   = '200';
        $log_data['return'] = json_encode($ret, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);

        $exceptionData = [];

        if ($exp) {
            $log_data['code'] = '500';

            if ($exp instanceof ErrorException) {
                $message = "{$exp->getName()}";
            } elseif ($exp instanceof \yii\base\Exception) {
                $message = "Exception ({$exp->getName()})";
            } else {
                $message = 'Exception';
            }
            $message .= " '" . get_class($exp) . "' with message '{$exp->getMessage()}' \n\nin "
                . $exp->getFile() . ':' . $exp->getLine() . "\n\n"
                . "Stack trace:\n" . $exp->getTraceAsString();

            $exceptionData['code']        = $log_data['exception.code'] = $exp->getCode();
            $exceptionData['message']     = $log_data['exception.message'] = $exp->getMessage();
            $exceptionData['file']        = $log_data['exception.file'] = $exp->getFile();
            $exceptionData['line']        = $log_data['exception.line'] = $exp->getLine();
            $exceptionData['stackstrace'] = $log_data['exception.stackstrace'] = $message;
        }

        // \Yii::$app->es->post(['baoxian-' . date('Y.m.d'), 'soap-log'], [],
        //     json_encode($log_data, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES));

        $url_host = parse_url($log_data['url'], PHP_URL_HOST);

        try {
            $eagleLog                  = EagleEye::createLog();
            $eagleLog->traceId         = $eagleCall['traceId'];
            $eagleLog->rpcId           = $eagleCall['rpcId']; //  $eagleCall['reqId']
            $eagleLog->rpcType         = 'ws';
            $eagleLog->serverHost      = gethostbyname($url_host);
            $eagleLog->serviceCategory = $this->name;
            $eagleLog->serviceName     = $log_data['url'];
            $eagleLog->methodName      = $log_data['method'];
            $eagleLog->requestDateTime = TimeUtils::ESDateTime($start_ts);

            // 出错或者开发环境记录完整请求
            if (!empty($exceptionData) || YII_DEBUG) {
                $eagleLog->requestJson = [
                    'params'  => $arguments,
                    'request' => [
                        'body'    => $log_data['request'],
                        'headers' => $log_data['request.headers'],
                    ],
                ];

                $eagleLog->responseJson = [
                    'return'    => $ret,
                    'exception' => $exceptionData,
                    'response'  => [
                        'body'    => $log_data['response'],
                        'headers' => $log_data['response.headers'],
                    ],
                ];
            } else {
                $eagleLog->requestJson  = ['params' => $arguments,];
                $eagleLog->responseJson = ['return' => $ret,];
            }
            $eagleLog->runTime = intval(round(($stop_ts - $start_ts) * 1000));

            if ($log_data['code'] == '200') {
                $eagleLog->result = 'OK';
            } else {
                $eagleLog->result = 'ERROR';
            }

            EagleEye::saveLog($eagleLog);
        } catch (\Exception $e) {
            \Yii::error($e->__toString(), 'eagle-eye-soap');
        }

        if ($exp) {
            throw $exp;
        }

        return $ret;
    }

    private function __encodeRequestHeaders($headers)
    {
        $hader = "";
        foreach ($headers as $key => $value) {
            $hader .= $key . ": " . $value . "\r\n";
        }

        return $hader;
    }
}